package cn.turboinfo.dongying.api.domain.web.component.file;

import cn.turboinfo.dongying.api.domain.common.service.file.FileAttachmentService;
import cn.turboinfo.dongying.api.entity.common.exception.file.FileAttachmentException;
import cn.turboinfo.dongying.api.entity.common.pojo.file.FileAttachment;
import cn.turboinfo.dongying.api.entity.common.pojo.file.FileAttachmentCreator;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Slf4j
@Component
@RequiredArgsConstructor
public class FileAttachmentHelper {

    public final FileAttachmentService fileAttachmentService;

    public FileAttachment uploadFile(InputStream inputStream, String filename, String relativePath, String originalFilename) {
        FileAttachmentCreator creator = FileAttachmentCreator.builder()
                .withDisplayName(originalFilename)
                .withFilename(filename)
                .withRelativePath(relativePath)
                .withOriginalFilename(originalFilename)
                .build();
        return uploadFile(inputStream, creator);
    }

    public FileAttachment uploadFile(InputStream inputStream, FileAttachmentCreator creator) {
        Path outputDirectory = fileAttachmentService.resolveAbsolutePath(creator.getRelativePath());

        // 保存文件
        try {
            FileUtils.copyInputStreamToFile(inputStream, outputDirectory.resolve(creator.getFilename()).toFile());
        } catch (IOException e) {
            throw new FileAttachmentException("保存上传文件出错", e);
        }

        return fileAttachmentService.save(creator);
    }

    public File readFileAttachment(Long fileAttachmentId) {
        FileAttachment fileAttachment = fileAttachmentService.getByIdEnsure(fileAttachmentId);
        return readFileAttachment(fileAttachment);
    }

    public File readFileAttachment(FileAttachment fileAttachment) {
        return new File(String.format("%s/%s", fileAttachmentService.resolveAbsolutePath(fileAttachment.getRelativePath()), fileAttachment.getFilename()));
    }

    public void deleteFileAttachment(Long fileAttachmentId) {
        FileAttachment fileAttachment = fileAttachmentService.getByIdEnsure(fileAttachmentId);
        deleteFile(fileAttachment.getFilename(), fileAttachment.getRelativePath());
        fileAttachmentService.deleteById(fileAttachment.getId());
    }

    private void deleteFile(String filename, String relativePath) {
        Path outputDirectory = fileAttachmentService.resolveAbsolutePath(relativePath);

        // 删除文件
        if (!outputDirectory.resolve(filename).toFile().delete()) {
            log.error("删除文件失败, filename={}, relativePath={}", filename, relativePath);
        }
    }

    /**
     * 检查并保存附件
     *
     * @param files                上传文件
     * @param allowExts            允许的扩展名
     * @param relativePath         相对路径
     * @param refType              引用类型
     * @param refId                引用ID
     * @param keepOriginalFilename 是否保存原始文件名
     * @return 返回保存后的对象列表
     * @throws IOException 保存出错异常
     */
    public List<FileAttachment> checkAndSaveFileAttachment(MultipartFile[] files, String[] allowExts, String relativePath, String refType, Long refId, boolean keepOriginalFilename) throws IOException {
        Set<String> allowExtSet = Stream.of(allowExts).map(String::toLowerCase).collect(Collectors.toSet());
        Pattern pattern = Pattern.compile("/([^\\w\\s\\d\\-_~,;:\\[\\]\\(\\).])|([\\.]{2,})/");

        List<FileAttachment> attachmentList = new ArrayList<>();
        for (MultipartFile file : files) {
            if (file.isEmpty()) {
                continue;
            }
            String originalFilename = file.getOriginalFilename();
            if (StringUtils.isBlank(originalFilename) || pattern.matcher(originalFilename).find()) {
                throw new RuntimeException("文件名不正确");
            }

            String fileExt = FilenameUtils.getExtension(originalFilename).toLowerCase();
            if (!allowExtSet.contains(fileExt)) {
                throw new RuntimeException("不支持的文件格式");
            }

            String displayName = originalFilename;
            String filename;
            if (!keepOriginalFilename) {
                originalFilename = UUID.randomUUID() + "." + fileExt;
                filename = originalFilename;
            } else {
                originalFilename = StringUtils.substring(originalFilename, -25);
                filename = String.format("%s_%s", UUID.randomUUID().toString().substring(0, 8), originalFilename);
            }

            try (InputStream inputStream = file.getInputStream()) {
                FileAttachmentCreator creator = FileAttachmentCreator.builder()
                        .withDisplayName(displayName)
                        .withFilename(filename)
                        .withOriginalFilename(originalFilename)
                        .withRefType(refType)
                        .withRefId(refId)
                        .withRelativePath(relativePath)
                        .build();
                attachmentList.add(uploadFile(inputStream, creator));
            }
        }
        return attachmentList;
    }

    /**
     * 检查并保存单一附件
     *
     * @param file                 上传文件
     * @param allowExts            允许的扩展名
     * @param relativePath         相对路径
     * @param refType              引用类型
     * @param refId                引用ID
     * @param keepOriginalFilename 是否保存原始文件名
     * @return 返回保存后的对象
     * @throws IOException 保存出错异常
     */
    public FileAttachment checkAndSaveFileAttachment(MultipartFile file, String[] allowExts, String relativePath, String refType, Long refId, boolean keepOriginalFilename) throws IOException {
        List<FileAttachment> attachmentList = checkAndSaveFileAttachment(new MultipartFile[]{file}, allowExts, relativePath, refType, refId, keepOriginalFilename);
        if (attachmentList.isEmpty()) {
            return null;
        }
        return attachmentList.get(0);
    }
}
